# -*- coding: utf-8 -*-

from odoo import models, fields, api, _
from odoo.exceptions import UserError
from datetime import timedelta


class SForecastMOLine(models.Model):
    _name = 's.forecast.mo.line'
    _description = 'Forecast MO Line'
    forecast_order_line_id = fields.Many2one('s.forecast.order.line', string='Forecast Order Line', required=True, ondelete='cascade')
    sale_order_line_id = fields.Many2one('sale.order.line', string='Sale Order Line', required=True)
    qty = fields.Integer(string='Quantity', required=True, default=1)


class SForecastOrderLine(models.Model):
    _name = 's.forecast.order.line'
    _description = 'Forecast Order Line'

    # Field definitions
    so = fields.One2many('sale.order', "sb_fo_line_id", string='SO', copy=False, readonly=True)
    forecast_mo_ids = fields.One2many('s.forecast.mo.line', 'forecast_order_line_id', string='Forecast MO Lines', copy=False, readonly=True)
    so_ids = fields.Many2many('sale.order', 's_forecast_order_line_sale_order_rel', 's_forecast_order_line_id', 'sale_order_id', string='SO Lines', copy=False, readonly=True)
    use_qty = fields.Integer(string='Used Quantity', compute='_compute_so_use_updated', store=True)
    use_percentages = fields.Integer(string='Use Percentages', compute='_compute_so_use_percentages', store=True)
    remain_qty = fields.Integer(string='Remaining Quantity', compute='_compute_so_use_percentages', store=True)
    forecast_order_id = fields.Many2one(
        's.forecast.order',
        string='Forecast Order',
        required=True,
        ondelete='cascade',
    )
    market_forecast_product_name = fields.Many2one(
        'customer.item.code',
        string='PN',
        required=True,
        help="Product name or description entered by the Marketing Department."  # Product name or description entered by the Marketing Department.
    )
    market_forecast_quantity = fields.Integer(
        string='Order Qty',  # Forecast quantity
        required=True,
        default=1
    )

    engineering_confirmed_product_id = fields.Many2one(
        'product.template',
        string='MPN',  # Engineering confirmed product (actual product)
        help="Actual product selected by the Engineering Department. If it's a new product, this field can be left blank, and 'New Product' should be checked."
        # Actual product selected by the Engineering Department. If it's a new product, this field can be left blank, and 'New Product' should be checked.
    )
    product_id = fields.Many2one('product.product', 'Product')
    preset_bom = fields.Boolean(string='Preset BOM', help='Use Preset BOM')  # Use Preset BOM

    # is_new_product = fields.Boolean(   #不做新的成品预测
    #     string='New Product',  # New Product
    #     help="Check this box if this is a new product, and the Engineering Department will manually add materials.",
    #     # Check this box if this is a new product, and the Engineering Department will manually add materials.
    #     default=False
    # )
    material_line_ids = fields.One2many(
        's.forecast.material.line',  # Updated to s.forecast.material.line
        'forecast_order_line_id',
        string='Material Requirements',
        copy=True
    )
    note = fields.Text(string='Note', help="Additional notes for this forecast order line.")  # Additional notes for this forecast order line.
    # State linked to the main order
    state = fields.Selection(
        related='forecast_order_id.state',
        string='State',  # State
        readonly=True,
        store=True
    )
    customer_dd = fields.Date(
        string='Customer DD',
        help="Customer Delivery Date",
        default=lambda self: (fields.Date.today() + timedelta(days=90))
    )
    pn_rev = fields.Char(
        string='PN Rev',
        help="PN Revision"
    )
    price_unit = fields.Float(
        string='Unit Price',
        help="Unit price for this forecast order line.",
        digits=(16, 4)
    )
    consumed = fields.Boolean(string='Consumed', default=False)
    so_use_updated = fields.Boolean(string='SO Use Updated', compute='_compute_so_use_updated', store=True)

    pm_quantity = fields.Integer('pm quantity', copy=False, readonly=True)  # 备料数量

    @api.depends('forecast_mo_ids', 'forecast_mo_ids.sale_order_line_id.order_id.state')
    def _compute_so_use_updated(self):
        for rec in self:
            v = sum(rec.forecast_mo_ids.filtered(lambda l: l.sale_order_line_id.order_id.state != 'cancel').mapped('qty'))
            rec.so_use_updated = (v >= rec.market_forecast_quantity)

    @api.depends('forecast_mo_ids.qty',
                 'forecast_mo_ids.sale_order_line_id.order_id.state',
                 'forecast_mo_ids')
    def _compute_so_use_percentages(self):
        for rec in self:
            sum_qty = sum(
                used.qty
                for used in rec.forecast_mo_ids
                if used.sale_order_line_id and used.sale_order_line_id.order_id.state != 'cancel'
            )
            if rec.market_forecast_quantity and sum_qty:
                rec.use_percentages = (sum_qty / rec.market_forecast_quantity) * 100
                rec.use_qty = sum_qty
                remain_qty = rec.market_forecast_quantity - sum_qty
                rec.remain_qty = remain_qty if remain_qty > 0 else 0
            else:
                rec.use_percentages = 0.0
                rec.use_qty = 0
                rec.remain_qty = rec.market_forecast_quantity

    # Helper function to recursively get all raw materials from a BOM
    def _get_all_raw_materials_from_bom(self, product, quantity, seen=None, parent_bom_line=None):
        if seen is None:
            seen = set()
        if product.id in seen:
            return {}
        seen.add(product.id)

        result = {}

        # 如果不是物料（例如成品或半成品），跳過
        if not product.bom_ids:
            if product.categ_id.id != 6:
                return {
                    product.id: {
                        'total_qty': quantity,
                        'bom_lines': [{
                            'bom_line_id': parent_bom_line.id if parent_bom_line else None,
                            'product_qty': parent_bom_line.product_qty if parent_bom_line else quantity,
                            'line_quantity': quantity,
                        }]
                    }
                }
            else:
                return {}

        # 找到 BOM
        bom_id = self.env['mrp.bom']._bom_find(products=product)
        bom = bom_id.get(product)
        if not bom:
            return {}

        for bom_line in bom.bom_line_ids:
            line_qty = bom_line.product_qty * quantity
            sub_result = self._get_all_raw_materials_from_bom(
                bom_line.product_id, line_qty, seen, parent_bom_line=bom_line
            )

            for prod_id, data in sub_result.items():
                if prod_id not in result:
                    result[prod_id] = {'total_qty': 0.0, 'bom_lines': []}
                result[prod_id]['total_qty'] += data['total_qty']
                result[prod_id]['bom_lines'].extend(data['bom_lines'])

        return result

    def compute_display_name(self):
        """Compute the display name for the forecast order line."""
        if self.market_forecast_product_name:
            return f"{self.market_forecast_product_name} ({self.engineering_confirmed_product_id.name})"
        return super(SForecastOrderLine, self).compute_display_name()

    @api.onchange('market_forecast_product_name')
    def _onchange_market_forecast_product_name(self):
        if self.market_forecast_product_name:
            # When the marketing forecast product name is changed, reset the engineering confirmed product
            if not self.forecast_order_id.customer_id:
                raise UserError(_("Please select a customer for the forecast order first."))
            mpn = self.market_forecast_product_name
            if not mpn:
                raise UserError(_("No MPN found for this PN %s." % self.market_forecast_product_name))
            self.engineering_confirmed_product_id = mpn.product_id.id
            self.product_id = mpn.product_id.product_variant_id.id
            self.pn_rev = mpn.version

    # When the engineering confirmed product changes, update the material list
    # @api.onchange('engineering_confirmed_product_id')
    def _onchange_engineering_confirmed_product_id(self):
        # Clear existing material lines
        self.material_line_ids = [(5, 0, 0)]
        if self.engineering_confirmed_product_id:
            # Get all raw materials and their aggregated quantities
            materials = self._get_all_raw_materials_from_bom(
                self.engineering_confirmed_product_id.product_variant_id,
                self.market_forecast_quantity
            )
            material_lines_data = []
            for product_id, data in materials.items():
                product = self.env['product.product'].browse(product_id)
                for line in data['bom_lines']:
                    material_lines_data.append((0, 0, {
                        'forecast_order_line_id': self.id,
                        'material_id': product.id,
                        'required_quantity': data['total_qty'],
                        'source': 'bom',  # All from BOM, even if it's the top product itself with no BOM
                        'base_bom_qty': line['product_qty'],  # Base BOM quantity for this material line
                    }))
            self.material_line_ids = material_lines_data
        else:
            # If no product, clear lines.
            self.material_line_ids = [(5, 0, 0)]

    # Update material lines (used for material calculation after state transition)
    def _update_material_lines(self):
        # This method will be called when the main order state changes
        # Ensure material quantities and stock are up-to-date
        for material_line in self.material_line_ids:
            material_line._compute_quantities()

    @api.onchange('market_forecast_quantity')
    def onchange_market_forecast_quantity(self):
        """Update material lines when the market forecast quantity changes."""
        if self.market_forecast_quantity <= 0:
            raise UserError(_("Market forecast quantity must be greater than zero."))
        else:
            # 更新物料明细汇总
            for line in self.material_line_ids:
                line.required_quantity = self.market_forecast_quantity * line.base_bom_qty

    def _compute_display_name(self):
        for res in self:
            if res.market_forecast_product_name and res.engineering_confirmed_product_id:
                res.display_name = res.market_forecast_product_name.code

    def action_forecast_mo_line(self):
        self.ensure_one()
        action = self.env["ir.actions.actions"]._for_xml_id("sb_forecast_order.s_forecast_mo_line_act_window")
        action['res_id'] = self.id
        return action
